﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Security;
using System.Security.Cryptography;
using System.IO;

namespace Client
{
    public partial class ClientForm : Form
    {
        /// <summary>
        /// Our net services for sending and receiving
        /// </summary>
        NetworkServices services;

        /// <summary>
        /// this is needed to make calls from a different thread to update the text box for server msg
        /// </summary>
        /// <param name="newText"></param>
        delegate void updateServerTextDelegate(string newText);

        /// <summary>
        /// this is needed to make calls from a different thread to update the connection buttons
        /// </summary>
        delegate void updateServerDisconnectedDelegate();

        /// <summary>
        /// Creates a new form
        /// </summary>
        public ClientForm()
        {
            InitializeComponent();

            // If DWM is not enabled then get out
            if (this.IsGlassEnabled())
            {
                // Try to fix the buttons
                // In this example, Black = Transparent
                connectButton.ForeColor = Color.DarkBlue;
                disconnectButton.ForeColor = Color.DarkBlue;
                registerButton.ForeColor = Color.DarkBlue;
                loginButton.ForeColor = Color.DarkBlue;
                
                // Add event handlers
                this.Paint += new PaintEventHandler(this.ClientForm_Paint);
                this.Resize += new System.EventHandler(this.ClientForm_Resize);

                // Fit glass to it
                this.FitGlass();
            }
        }

        // I was having fun trying to enable Vista Glass on this application, but it
        // tends to glass the button text as well, making it unreadable. Please ignore :)
        #region Vista Glass
        // margins for API call used in Paint
        private VistaApi.Margins marg;

        // Rectangles; one for each edge of the form
        private Rectangle topRect = Rectangle.Empty;
        private Rectangle botRect = Rectangle.Empty;
        private Rectangle lefRect = Rectangle.Empty;
        private Rectangle rigRect = Rectangle.Empty;

        /// <summary>
        /// Fit Vista Glass to boundaries of Form
        /// </summary>
        private void FitGlass()
        {
            // If DWM is not enabled then get out
            if (!this.IsGlassEnabled())
            {
                return;
            }

            // Set the Margins to their default values
            int padding = 4;
            marg.Top = connectButton.Bottom + padding + 2; // extend from the top
            marg.Left = connectButton.Left - padding;  // extend from left
            marg.Right = this.ClientSize.Width - loginDetailsGroupBox.Right - padding;
            marg.Bottom = this.ClientSize.Height - chatMessageGroupBox.Bottom - padding;

            // call the function that gives us glass, 
            // passing a reference to our inset Margins
            VistaApi.DwmExtendFrameIntoClientArea(this.Handle, ref marg);
            this.Invalidate();
        }

        /// <summary>
        /// Check if Glass is enabled on this machine
        /// </summary>
        /// <returns></returns>
        private bool IsGlassEnabled()
        {
            if (Environment.OSVersion.Version.Major < 6)
            {
                //Debug.WriteLine("How about trying this on Vista?");
                return false;
            }

            //Check if DWM is enabled
            bool isGlassSupported = false;
            VistaApi.DwmIsCompositionEnabled(ref isGlassSupported);
            return isGlassSupported;
        }

        /// <summary>
        /// Alpha-blending Paint after the glass extension
        /// this seems better than the winforms transparency approach because here we can click on the glass!
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void ClientForm_Paint(object sender, PaintEventArgs e)
        {
            // black brush for Alpha transparency
            SolidBrush blackBrush = new SolidBrush(Color.Black);

            Graphics g = e.Graphics;

            if (this.IsGlassEnabled())
            {
                // setup the rectangles
                topRect = new Rectangle(0, 0, this.ClientSize.Width, marg.Top);
                lefRect = new Rectangle(0, 0, marg.Left, ClientSize.Height);
                rigRect = new Rectangle(this.ClientSize.Width - marg.Right, 0, marg.Right, this.ClientSize.Height);
                botRect = new Rectangle(0, this.ClientSize.Height - marg.Bottom, this.ClientSize.Width, marg.Bottom);

                // Fill Rectangles
                g.FillRectangle(blackBrush, topRect);
                g.FillRectangle(blackBrush, lefRect);
                g.FillRectangle(blackBrush, rigRect);
                g.FillRectangle(blackBrush, botRect);
            }

            blackBrush.Dispose();
        }

        /// <summary>
        /// Override WndProc to pick up relevant Windows messages
        /// </summary>
        /// <param name="m"></param>
        protected override void WndProc(ref Message m)
        {
            base.WndProc(ref m);

            if (m.Msg == VistaApi.WM_NCHITTEST // if this is a click
              && m.Result.ToInt32() == VistaApi.HTCLIENT // ...and it is on the client
              && this.IsOnGlass(m.LParam.ToInt32())) // ...and specifically in the glass area
            {
                m.Result = new IntPtr(VistaApi.HTCAPTION); // lie and say they clicked on the title bar
            }
        }

        /// <summary>
        /// Did the user click a point that is glassed?
        /// </summary>
        /// <param name="lParam"></param>
        /// <returns></returns>
        private bool IsOnGlass(int lParam)
        {
            // sanity check
            if (!this.IsGlassEnabled())
            {
                return false;
            }

            // get screen coordinates
            int x = (lParam << 16) >> 16; // lo order word
            int y = lParam >> 16; // hi order word

            // translate screen coordinates to client area
            Point p = this.PointToClient(new Point(x, y));

            // work out if point clicked is on glass
            if (topRect.Contains(p) || lefRect.Contains(p) || rigRect.Contains(p) || botRect.Contains(p))
            {
                return true;
            }

            return false;
        }

        private void ClientForm_Resize(object sender, EventArgs e)
        {
            FitGlass();
        }
        #endregion

        /// <summary>
        /// Our event handler to register for server messages, passes through to our delegate since this
        /// is called from the listening thread
        /// </summary>
        /// <param name="sender">the object that sent the message</param>
        /// <param name="args">the message that we got</param>
        public void OnServerMessageReceived(object sender, ServerEventArgs args)
        {
            this.UpdateServerText(args.message);
        }

        /// <summary>
        /// A delegate to handle problem of cross-thread modification
        /// of a control.  Windows wants gui to be updated only from the 
        /// main thread.
        /// </summary>
        /// <param name="text">the text to write</param>
        public void UpdateServerText(string text)
        {
            if (serverMessageTextWindow.InvokeRequired)
            {
                // we were invoked from another thread
                updateServerTextDelegate del = new updateServerTextDelegate(UpdateServerText);
                serverMessageTextWindow.Invoke(del, new object[] { text });
            }
            else
            {
                // we were invoked from main thread
                serverMessageTextWindow.AppendText(text + "\n");
            }
        }

        /// <summary>
        /// Our event handler to register for server messages, passes through to our delegate since this 
        /// is called from the listening thread
        /// </summary>
        /// <param name="sender">the object that sent the message</param>
        public void OnServerDisconnected(object sender)
        {
            this.UpdateServerDisconnected();
        }

        public void UpdateServerDisconnected()
        {
            if (connectButton.InvokeRequired)
            {
                // we were invoked from another thread
                updateServerDisconnectedDelegate del = new updateServerDisconnectedDelegate(UpdateServerDisconnected);
                connectButton.Invoke(del);

            }
            else
            {
                // we were invoked from the main thread, capt'n
                if (disconnectButton.Enabled)
                {
                    services = null;
                    OnDisconnectClicked(null, null);
                }
            }
        }

        /// <summary>
        /// Handle the connect button press. 
        /// Enable our three disabled buttons and set up network connection.  register our event listener
        /// for the server listening 
        /// </summary>
        /// <param name="sender">sender of the message</param>
        /// <param name="e">the event args</param>
        private void OnConnectClicked(object sender, EventArgs e)
        {
            LoginDialog ld = new LoginDialog();
            if (ld.ShowDialog() == DialogResult.OK)
            {
                try
                {
                    services = new NetworkServices(ld.Host, ld.Port);
                    services.OnMessageReceived += new NetworkServices.ServerMessageReceived(OnServerMessageReceived);
                    services.OnDisconnected += new NetworkServices.ServerDisconnected(OnServerDisconnected);
                    loginButton.Enabled = true;
                    registerButton.Enabled = true;
                    connectButton.Enabled = false;
                    disconnectButton.Enabled = true;
                    sendMessageButton.Enabled = true;
                }
                catch (System.Net.Sockets.SocketException ex)
                {
                    MessageBox.Show(ex.Message, "Socket Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
                }
            }
        }

        /// <summary>
        /// Send the BYE message to the server when disconnect button is pressed, shutdown the connection
        /// and setup the button states
        /// </summary>
        /// <param name="sender">the sender of the event</param>
        /// <param name="e">the arguments</param>
        private void OnDisconnectClicked(object sender, EventArgs e)
        {
            // DONE: Put code here to send the BYE message and shutdown the connection
            if (services != null)
            {
                services.Write("BYE");
                services.Shutdown();
            }

            // reset button states
            registerButton.Enabled = false;
            loginButton.Enabled = false;
            connectButton.Enabled = true;
            disconnectButton.Enabled = false;
            sendMessageButton.Enabled = false;
        }

        /// <summary>
        /// Send the register message to the server when button is pressed
        /// </summary>
        /// <param name="sender">the sender of the event</param>
        /// <param name="e">the standard arguments</param>
        private void OnRegisteredClicked(object sender, EventArgs e)
        {
            // DONE: Put code here to send the register message
            string username = this.nameField.Text;
            string password = this.passwordField.Text;

            // Encrypt password
            password = Encrypt(password);

            // Escape invalid xml characters
            username = SecurityElement.Escape(username);
            password = SecurityElement.Escape(password);

            // Pack and send the string
            string command = "REGISTER\t" + username + "\t" + password + "\n";
            services.Write(command);
        }

        /// <summary>
        /// Send the login message to the server when the button is pressed
        /// </summary>
        /// <param name="sender">the sender of event</param>
        /// <param name="e">the standard args</param>
        private void OnLoginClicked(object sender, EventArgs e)
        {
            // DONE: Put code here to send the login message to the server
            string username = this.nameField.Text;
            string password = this.passwordField.Text;

            // Encrypt password
            password = Encrypt(password);

            // escape invalid xml characters
            username = SecurityElement.Escape(username);
            password = SecurityElement.Escape(password);

            // Pack and send the string
            string command = "LOGIN\t" + username + "\t" + password + "\n";
            services.Write(command);
        }

        /// <summary>
        /// Wrap the encryption key, etc to simplify calling Encrypt
        /// </summary>
        /// <param name="Text">string to be encrypted</param>
        /// <returns>encrypted string</returns>
        public static string Encrypt(string Text)
        {
            // REALLY encrypt password here.
            byte[] salt = { 0, 0, 0, 0, 0, 0, 0, 0 };
            byte[] V = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 };
            PasswordDeriveBytes cdk = new PasswordDeriveBytes(Text, salt);
            byte[] kex = cdk.CryptDeriveKey("RC2", "SHA1", 128, salt);

            return Encrypt(Text, kex, V);
        }

        /// <summary>
        /// This was a serious pain to do. We tried fifty different ways using various examples,
        /// (mostly focusing on http://www.codeproject.com/KB/security/using_cryptostream.aspx )
        /// and had about as many errors trying to get it to work.
        /// We eventually found http://www.codeproject.com/KB/cs/Encryption_Decryption.aspx
        /// and copied the code he uses.
        /// 
        /// Really, though, to truly use encryption the assignment parameters should be changed.
        /// An attacker (let's call her Eve) could sniff the packets you send and easily write
        /// her own application that simply uses the sniffed encrypted password as the real
        /// password, as that's all that's stored on the server.
        /// 
        /// So while this code technically fulfils the extra credit requirements, it's not
        /// truly improving the security of the system, it's simply obfuscating the user's 
        /// password. You could run a one-way hash on their password and obtain similar results.
        /// 
        /// Perhaps the password sent over the line could be based on a changing factor, such as
        /// time (today's date?). This method also requires the server to decrypt the password 
        /// upon receipt, and reencrypt it based on a fixed key for storage on the server.
        /// </summary>
        /// <param name="Text">string to be encrypted</param>
        /// <param name="key">byte array containing the key</param>
        /// <param name="VectorBytes">byte array containing the initialization vector</param>
        /// <returns>encrypted string</returns>
        public static string Encrypt(string Text, byte[] key, byte[] VectorBytes)
        {
            try
            {
                byte[] TextBytes = Encoding.UTF8.GetBytes(Text);
                RijndaelManaged rijKey = new RijndaelManaged();
                rijKey.Mode = CipherMode.CBC;
                ICryptoTransform encryptor = rijKey.CreateEncryptor(key, VectorBytes);
                MemoryStream memoryStream = new MemoryStream();
                CryptoStream cryptoStream = new CryptoStream(memoryStream, encryptor, CryptoStreamMode.Write);
                cryptoStream.Write(TextBytes, 0, TextBytes.Length);
                cryptoStream.FlushFinalBlock();
                byte[] cipherTextBytes = memoryStream.ToArray();
                memoryStream.Close();
                cryptoStream.Close();
                string cipherText = Convert.ToBase64String(cipherTextBytes);
                return cipherText;
            }
            catch (Exception e)
            {
                MessageBox.Show("Falsches Passwort " + e.Message.ToString());
                string t = "";
                return t;
            }
        }

        /// <summary>
        /// Handle the send message button pressed in the chat input.  
        /// Send the text to the server 
        /// as a CHAT message
        /// </summary>
        /// <param name="sender">the sender</param>
        /// <param name="e">the stnd args</param>
        private void OnSendMessageClicked(object sender, EventArgs e)
        {
            // DONE: Put code here to send a chat message to the server
            string chat = this.chatText.Text;
            string command = "CHAT\t" + chat + "\n";
            services.Write(command);
        }

        private void ClientForm_FormClosing(object sender, FormClosingEventArgs e)
        {
            if (disconnectButton.Enabled)
            {
                OnDisconnectClicked(sender, e);
            }

            base.OnClosing(e);
        }

        private void chatText_Enter(object sender, EventArgs e)
        {
            if(this.chatText.Text.Equals("Enter Message Here..."))
            {
                this.chatText.Text = "";
            }
        }
    }
}